package biz.datalk.industrialland.common.component.excpetion;

import biz.datalk.industrialland.common.def.CommonConstant;
import biz.datalk.industrialland.common.exception.DatalkTokenException;
import biz.datalk.industrialland.common.result.UnifyResultCode;
import biz.datalk.industrialland.common.util.ExceptionUtil;
import biz.datalk.industrialland.common.util.I18NUtil;
import biz.datalk.industrialland.common.util.ReqErrUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.util.PathMatcher;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * 异常处理器
 *
 * 处理从 Filter 中抛出来的异常
 *
 * @author tarofang@163.com
 * @date 2020-01-09
 */
@Slf4j
// @Component("globalFilterExceptionHandler")
// @Order(Ordered.HIGHEST_PRECEDENCE + 999)
public class GlobalFilterExceptionHandler extends OncePerRequestFilter {

    private RequestMappingHandlerMapping requestMappingHandlerMapping;

    @Autowired
    public void setRequestMappingHandlerMapping(RequestMappingHandlerMapping requestMappingHandlerMapping) {
        this.requestMappingHandlerMapping = requestMappingHandlerMapping;
    }

    private final PathMatchingResourcePatternResolver resourceLoader = new PathMatchingResourcePatternResolver();

    @Value("${datalk.core.hiddenPages:}")
    private String hiddenPagesStr;

    private String[] hiddenPages;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        try {
            // 不显示页面直接重定向到 404界面
            String reqUri = request.getRequestURI();
            if (isHiddenPage(reqUri)) {
                response.setStatus(404);
                ReqErrUtil.reqPage(request, response, "", "", CommonConstant.ERR_PAGE_404, false);
                return;
            }

            filterChain.doFilter(request, response);
        } catch (Exception ex) {
            chechExceptionHandler(request, response, ex);
        }
    }

    protected void chechExceptionHandler(HttpServletRequest request, HttpServletResponse response, Exception ex) throws ServletException, IOException {
        // if (log.isDebugEnabled()) {
        //     Enumeration<String> attributeNames = request.getAttributeNames();
        //     while (attributeNames.hasMoreElements()) {
        //         String s = attributeNames.nextElement();
        //         Object attribute = request.getAttribute(s);
        //         log.debug("{} ==> {} ==> {}", s, attribute, attribute.getClass());
        //     }
        // }

        /* false - 不继续执行，直接返回  true - 继续执行 */
        if (!checkRequestUriExists(request, response, ex)) {
            return;
        }

        Throwable rootCause = ExceptionUtil.getRootCauseWithSelf(ex);

        if (rootCause instanceof DatalkTokenException) {
            datalkTokenExceptionResolver(request, response, (DatalkTokenException) rootCause);
            return;
        }

        // org.apache.tomcat.util.http.fileupload
        // if (ex instanceof org.springframework.web.multipart.MultipartException) {
        //     System.out.println("xxxxxxxxx");
        // }

        throw new ServletException(ex);
    } /// ~

    protected boolean checkRequestUriExists(HttpServletRequest request, HttpServletResponse response, Exception ex) throws ServletException {
        if (requestMappingHandlerMapping == null) {
            return true;
        }

        try {

            HandlerExecutionChain handler = requestMappingHandlerMapping.getHandler(request);
            // 未找到对应的处理器 => 请求资源不存在
            if (handler == null) {
                ReqErrUtil.reqPrint(request, response, UnifyResultCode.ERROR_REQUEST_NOT_FOUND, I18NUtil.getMsg("error.request.not.found"), CommonConstant.ERR_PAGE_404);
                return false;
            }

            return true;
        } catch (Exception e) {
            // 不支持该请求方式
            if (e instanceof HttpRequestMethodNotSupportedException) {
                ReqErrUtil.reqPrint(request, response, UnifyResultCode.METHOD_NO_SUPPORT_ERROR, I18NUtil.getMsg("error.request.method.no.support"), CommonConstant.ERR_PAGE_403);
                return false;
            }
            throw new ServletException(ex);
        }
    }

    protected void datalkTokenExceptionResolver(HttpServletRequest request, HttpServletResponse response, DatalkTokenException ex) throws ServletException {
        // ReqErrUtil.reqPrint(request, response, UnifyResultCode.TOKEN_TIMEOUT, I18NUtil.getMsg("login.token.timeout"), CommonConstant.ERR_PAGE_403);
        ReqErrUtil.reqPrint(request, response, ex.getCode(), ex.getMessage(), CommonConstant.ERR_PAGE_403);
        //ReqErrUtil.reqApi(response, UnifyResultCode.APPLICATION_ERROR, I18NUtil.getMsg("loginpage.unable.hint"));
    }

    protected boolean isHiddenPage(String uri) {
        if (hiddenPages == null) {
            if (StringUtils.isBlank(this.hiddenPagesStr)) {
                return false;
            }
            String[] split = StringUtils.split(this.hiddenPagesStr, ",");
            // if (split == null || split.length <= 0) {
            if (ArrayUtils.getLength(split) <= 0) {
                this.hiddenPages = new String[]{};
                return false;
            }

            List<String> resList = new ArrayList<>();
            String tmp;
            for (String pattern : split) {
                tmp = StringUtils.trimToNull(pattern);
                if (tmp == null) {
                    continue;
                }
                resList.add(tmp);
            }
            this.hiddenPages = resList.toArray(new String[0]);
            return matcher(uri);
        }

        return matcher(uri);
    }

    protected boolean matcher(String uri) {
        // if (hiddenPages == null || hiddenPages.length <= 0) {
        if (ArrayUtils.getLength(this.hiddenPages) <= 0) {
            return false;
        }
        PathMatcher pathMatcher = resourceLoader.getPathMatcher();
        boolean result = false;
        for (String excludePattern : hiddenPages) {
            result = pathMatcher.match(excludePattern, uri);
            if (result) {
                break;
            }
        }
        return result;
    }

}
